#pragma once

#include <memory>
#include <cmath>
#include "autograd/autograd_extension.h"

namespace  ldl
{
namespace autograd
{
template<typename T>
class Function;

template<typename T>
class Variable;

template<typename T>
class Autograd
{
public:
    Autograd(const T& data, const std::string& name) {
        m_data = std::make_shared<T>(data);
        m_name = name;
    }

    Autograd(const T& data) {
        m_data = std::make_shared<T>(data);
    }

    Autograd(const T&& data) {
        m_data = std::make_shared<T>(data);
    }

    Autograd() {
    }

    virtual ~Autograd() = default;

    void set_creator(std::shared_ptr<Function<T>> creator) {
        m_creator = creator;
    }

    std::shared_ptr<Function<T>> creator() const{
        return m_creator;
    }

    std::shared_ptr<T>& data() {
        return m_data;
    }

    std::shared_ptr<Variable<T>>& grad() {
        return m_grad;
    }

    int64_t generation() {
        return m_generation;
    }

    void set_generation(int64_t generation) {
        m_generation = generation;
    }

    const std::string& name() const {
        return m_name;
    }

    std::string str() const {
        std::ostringstream oss;
        if(this->m_grad != nullptr) {
            oss << *this << "(" << (*(this->m_grad)) << ")";
        } else {
            oss << *this << "(" << "NULL" << ")";
        }

        return oss.str();
    }

    friend std::ostream& operator<<(std::ostream& os, const Autograd<T>& var)
    {
        return os << *(var.m_data);
    }

private:
    std::shared_ptr<T> m_data{};
    std::shared_ptr<Variable<T>> m_grad{};
    std::shared_ptr<Function<T>> m_creator{};
    int64_t m_generation{};
    std::string m_name{};
};
}
}
